import copy
from collections import OrderedDict


def adjust_ordered_dict_order(od, key, position):
    """
    调整 OrderedDict 中指定键的位置。

    :param od: OrderedDict 对象
    :param key: 要调整位置的键
    :param position: 目标位置，0 表示插入到开头，-1 表示插入到末尾
    :return: 调整后的 OrderedDict
    """
    # 检查键是否存在于 OrderedDict 中
    if key not in od:
        raise KeyError(f"Key '{key}' not found in the OrderedDict")
    od = od.copy()

    # 获取键对应的值
    value = od.pop(key)

    # 创建新的 OrderedDict
    new_od = OrderedDict()

    # 根据位置插入
    if position == 0:
        new_od[key] = value  # 插入到开头
        for k, v in od.items():
            new_od[k] = v
    elif position == -1:
        for k, v in od.items():
            new_od[k] = v
        new_od[key] = value  # 插入到末尾
    else:
        raise ValueError("Invalid position. Use 0 for the beginning or -1 for the end.")

    return new_od


class FormulaCalculation:
    """
    配方计算类，用于处理配方相关的计算和操作。
    """
    EQUIPMENT = {0: "冶炼厂", 1: "构筑站", 2: "装配站", 3: "", 4: "制造站", 5: "", 6: "抽水站", 7: "煤炭发电机", 8: "铸造站", 9: "油井",
                 10: "精炼站",
                 11: "",
                 12: "",
                 13: "",
                 14: "", 15: "", 16: "", 17: "", 18: ""}

    def __init__(self, name, rate, production, equipment_num=None):
        """
        初始化FormulaCalculation类的实例。

        :param name: 配方的名称。
        :param rate: 配方的生产速率。
        :param production: 配方的生产产量。
        """
        self.name: str = name  # 配方名称
        self.production = production
        self._target_production = None
        self.super_formula = None
        self.raw_material: OrderedDict[str:list[FormulaCalculation, int]] = OrderedDict()  # 原材料的有序字典{原料：数量}
        self.after_product: OrderedDict[str:list[FormulaCalculation, int]] = OrderedDict()  # 副产品
        self.rate: int = rate  # 配方生产速率（个/分钟）
        if equipment_num is None:
            self.equipment_index = self.length
            self.assign_equipment = False
        else:
            self.equipment_index = equipment_num
            self.assign_equipment = True

    @property
    def children(self):
        return (value[0] for value in self.raw_material.values())

    @property
    def target_production(self):
        """
        计算并返回目标产量。

        此属性根据不同的条件计算目标产量。如果 `self.super_formula` 为空，
        它首先检查 `self._target_production` 是否为空。如果两者都为空，则返回 `self.production`。
        如果 `self.super_formula` 不为空，它使用一个特定的公式来计算目标产量，
        该公式涉及到 `super_formula` 的属性和方法。

        Returns:
            float: 目标产量。
        """
        # 检查 super_formula 是否为空
        if self.super_formula is None:
            # 如果 _target_production 也为空，返回 production
            if self._target_production is None:
                return self.production
            else:
                # 否则返回 _target_production
                return self._target_production
        else:
            # 如果 super_formula 不为空，使用特定公式计算目标产量
            return self.super_formula.target_production / self.super_formula.production \
                   * self.super_formula.raw_material[self.name][1]

    @property
    def target_after_production(self):
        """
        计算并返回生产后的目标产量。仅当当前物品为副产物项时此项有效

        此方法首先检查是否存在一个超级公式（super_formula），如果不存在，将抛出一个错误。
        进一步地，如果超级公式的 after_product 属性为空，也将抛出一个错误。
        最后，方法将返回该实例在超级公式中的目标产量，该产量是根据特定的名称在超级公式
        的 after_product 字典中查找，并根据超级公式的目标生产和实际生产量进行比例计算。

        Raises:
            ValueError: 如果 super_formula 或 super_formula.after_product 不存在。

        Returns:
            float: 计算后的目标产量。
        """
        if self.super_formula is None:
            raise ValueError("super_formula is None")
        if self.super_formula.after_product == {}:
            raise ValueError("super_formula.after_product is None")
        return self.super_formula.after_product[self.name][1] * self.super_formula.target_production \
               / self.super_formula.production

    @target_production.setter
    def target_production(self, value):
        self._target_production = value

    @property
    def equipment(self):
        """
        获取设备名称。
        """
        return FormulaCalculation.EQUIPMENT[self.equipment_index]

    @property
    def equipment_num(self, ndigits=1):
        """
        计算所需的设备数量。

        该属性用于计算达到目标产量时所需的设备数量。通过除以生产速率来估算所需的设备数量，并对结果进行四舍五入。

        :param ndigits: 保留的小数位数，默认为1位。
        :return: 设备数量，根据生产速率计算得出。
        """
        return round(self.target_production / self.rate, ndigits)

    @property
    def length(self):
        """
        获取原材料的数量。

        :return: 原材料的种类数量。
        """
        return len(self.raw_material)

    def add_raw_material(self, formula, quantity):
        """
        向原材料字典中添加一种配方及其对应的数量。

        :param formula: 配方名称。
        :param quantity: 配方的数量。
        :return: 无返回值。
        """
        formula = copy.deepcopy(formula)
        formula.super_formula = self
        self.raw_material[formula.name] = [formula, quantity]
        if not self.assign_equipment:
            self.equipment_index = self.length

    def add_after_product(self, formula, quantity):
        """
        将配方和数量添加到字典after_product中，以配方名称作为键。

        此方法用于在执行某些操作后，指定一个产品及其对应的数量。它通过深拷贝的方式
        来复制配方对象，以确保原始配方对象不会被意外修改。

        参数:
        formula: Recipe对象，表示要添加的产品的配方。
        quantity: int，表示要添加的产品的数量。
        """
        formula = copy.deepcopy(formula)
        formula.super_formula = self
        self.after_product[formula.name] = [formula, quantity]

    def change_formula(self, old_formula_name, new_formula):
        """
        递归地替换具有指定名称的所有节点及其对应的字典键。
        :param old_formula_name: 要替换的目标节点名称
        :param new_formula: 新的节点对象
        """
        datas_to_replace = {}
        for key, value in self.raw_material.items():
            child, data = value
            if child.name == old_formula_name:
                new_node_ = copy.deepcopy(new_formula)
                new_node_.super_formula = self
                # 如果找到了匹配的节点，则记录其键以备后续替换
                datas_to_replace[key] = [new_node_, data]
            else:
                # 递归调用子节点的replace_nodes_by_name方法
                child.change_formula(old_formula_name, new_formula)

        # 替换所有找到的匹配节点及其对应的字典键
        for key, value in datas_to_replace.items():
            del self.raw_material[key]
            new_key = new_formula.name
            self.raw_material[new_key] = value

    def traverse_with_depth(self):
        """
        遍历当前配方及其子配方，并返回每个子配方的深度和引用。

        该方法用于遍历配方树中的每个子节点（子配方），并返回该子节点的深度和引用。
        遍历开始于当前节点（self），并递归地遍历每个子配方。

        :return: 生成器，返回两个值的元组。第一个值是子配方的深度（从0开始），第二个值是子配方的引用。
        """
        yield 0, self  # 返回当前节点及其深度为0
        for formula, quantity in self.raw_material.values():
            formula: FormulaCalculation
            for depth, sub_formula in formula.traverse_with_depth():
                yield depth + 1, sub_formula

    def get_after_product_str(self):
        """
        获取当前物品为副产物时，生产信息的的字符串表示

        返回:
            str: 格式化的生产后字符串，包括产品名称和生产速率。如果相关属性不存在或值不合法，返回空字符串。
        """
        try:
            # 构造生产后的字符串表示，包括产品名称和生产速率
            return f"{self.name}：{self.target_after_production}个/分钟"
        except ValueError:
            # 如果发生ValueError异常，返回空字符串
            return ""

    def get_produce_str(self):
        """
        生成并返回一个字符串，描述生产效率和相关设备信息。

        该方法首先构造一个字符串，包含以下信息：
        - 生产名称
        - 目标生产数量（每分钟）
        - 使用的设备名称
        - 设备数量

        如果存在副产品（after_product 不为空字典），则将副产品信息也追加到字符串中。
        副产品的信息包括产品名称和数量（每分钟），数量根据目标生产数量和生产效率进行换算。

        :return: 描述生产效率和设备信息的字符串
        """
        # 构造基础生产信息字符串
        produce_str = f"{self.name}：{self.target_production}个/分钟，设备：{self.equipment}×" \
                      f"{self.equipment_num}个"
        # 如果存在副产品，将其信息追加到字符串中
        if self.after_product != {}:
            produce_str += \
                f" --->>>副产品：{', '.join([formula.get_after_product_str() for formula, quantity in self.after_product.values()])}"

        return produce_str

    def get_formula_sheet(self, target_production=None):
        """
        获取配方表。
        """
        if target_production is not None:
            self.target_production = target_production
        collect_dict = OrderedDict()  # 原料汇总信息
        after_product_dict = OrderedDict()  # 副产物汇总信息

        # 打印流程信息
        print("\n\n" + "\\/---" * 30)
        print(f"-->{self.name}：{self.target_production}个/分钟 生产信息")
        print("-\t->流程树：")
        for depth, formula in self.traverse_with_depth():
            formula: FormulaCalculation

            print("-\t" + ' ' * (4 * depth) + f'|->{formula.get_produce_str()}')

            # 添加原料汇总信息
            if formula.name not in collect_dict:
                collect_dict[formula.name] = ([formula], depth)
            else:
                if all(depth > item for item in (d[1] for d in collect_dict.values())):
                    collect_dict = adjust_ordered_dict_order(collect_dict, formula.name, -1)
                collect_dict[formula.name][0].append(formula)

            # 添加副产品汇总信息
            for _formula, _ in formula.after_product.values():
                if _formula.name not in after_product_dict:
                    after_product_dict[_formula.name] = [_formula]
                else:
                    after_product_dict[_formula.name].append(_formula)

        # 打印原料汇总信息
        print("\n-\t->原料汇总：")
        for formula_name, formula_list in collect_dict.items():
            formula_list = formula_list[0]
            target_production = sum(formula.target_production for formula in formula_list)
            equipment_num = round(sum(formula.equipment_num for formula in formula_list), 1)
            print(
                f"-\t\t->{formula_name}：{target_production}个/分钟，"
                f"{formula_list[0].equipment}×{equipment_num}个")
        # 打印副产品汇总信息
        if after_product_dict:
            print("\n-\t->副产品汇总：")
            for formula_name, formula_list in after_product_dict.items():
                target_production = sum(formula.target_after_production for formula in formula_list)
                print(f"-\t\t->{formula_name}：{target_production}个/分钟")
        print("/\\---" * 30)


if __name__ == '__main__':
    pass
    iron_ore = FormulaCalculation("铁矿石", 30, 1)

    iron_ingot = FormulaCalculation("铁锭", 30, 1, equipment_num=0)
    iron_ingot.add_raw_material(iron_ore, 1)

    iron_plate = FormulaCalculation("铁板", 20, 2)
    iron_plate.add_raw_material(iron_ingot, 3)

    iron_bar = FormulaCalculation(name="铁棒", rate=15, production=1)
    iron_bar.add_raw_material(iron_ingot, 1)

    screw = FormulaCalculation(name="螺丝", rate=40, production=4)
    screw.add_raw_material(iron_bar, 1)

    reinforced_iron_plate = FormulaCalculation(name="强化铁板", rate=5, production=1)
    reinforced_iron_plate.add_raw_material(iron_plate, 6)
    reinforced_iron_plate.add_raw_material(screw, 12)

    rotor = FormulaCalculation(name="转子", rate=4, production=1)
    rotor.add_raw_material(iron_bar, 5)
    rotor.add_raw_material(screw, 25)

    smart_panel = FormulaCalculation(name="智能嵌板", rate=2, production=1)
    smart_panel.add_raw_material(reinforced_iron_plate, 1)
    smart_panel.add_raw_material(rotor, 1)

    copper_ore = FormulaCalculation(name="铜矿石", rate=30, production=1)

    copper_ingot = FormulaCalculation(name="铜锭", rate=30, production=1)
    copper_ingot.add_raw_material(copper_ore, 1)

    electric_wire = FormulaCalculation(name="电线", rate=30, production=2)
    electric_wire.add_raw_material(copper_ingot, 1)

    cable = FormulaCalculation(name="电缆", rate=30, production=1)
    cable.add_raw_material(electric_wire, 2)

    copperplate = FormulaCalculation(name="铜板", rate=10, production=1)
    copperplate.add_raw_material(copper_ingot, 2)

    coal = FormulaCalculation(name="煤炭", rate=30, production=1)

    water = FormulaCalculation(name="水", rate=120, production=1, equipment_num=6)

    coal_power = FormulaCalculation(name="煤炭发电", rate=1, production=1, equipment_num=7)
    coal_power.add_raw_material(coal, 15)
    coal_power.add_raw_material(water, 45)

    modular_frame = FormulaCalculation(name="模块化框架", rate=2, production=2, equipment_num=2)
    modular_frame.add_raw_material(iron_bar, 12)
    modular_frame.add_raw_material(reinforced_iron_plate, 3)

    steel_ingot = FormulaCalculation(name="钢锭", rate=45, production=3, equipment_num=8)
    steel_ingot.add_raw_material(iron_ore, 3)
    steel_ingot.add_raw_material(coal, 3)

    steel_beam = FormulaCalculation(name="钢梁", rate=15, production=1, equipment_num=1)
    steel_beam.add_raw_material(steel_ingot, 4)

    steel_tube = FormulaCalculation(name="钢管", rate=20, production=2, equipment_num=1)
    steel_tube.add_raw_material(steel_ingot, 3)

    multifunctional_framework = FormulaCalculation(name="多功能框架", rate=5, production=2, equipment_num=2)
    multifunctional_framework.add_raw_material(steel_beam, 12)
    multifunctional_framework.add_raw_material(modular_frame, 1)

    SMA_substance = FormulaCalculation(name="SMA物质", rate=30, production=1)

    active_SMA_substance = FormulaCalculation(name="活性SMA物质", rate=30, production=1)
    active_SMA_substance.add_raw_material(SMA_substance, 4)
    # active_SMA_substance.get_formula_sheet(30) # 打印

    limestone = FormulaCalculation(name="石灰石", rate=30, production=1)

    concrete = FormulaCalculation(name="混凝土", rate=15, production=1, equipment_num=1)
    concrete.add_raw_material(limestone, 3)

    reinforced_concrete_beam = FormulaCalculation(name="钢筋混凝土梁", rate=6, production=1, equipment_num=2)
    reinforced_concrete_beam.add_raw_material(concrete, 6)
    reinforced_concrete_beam.add_raw_material(steel_beam, 3)

    heavy_duty_modular_framework = FormulaCalculation(name="重型模块化框架", rate=2, production=2, equipment_num=4)
    heavy_duty_modular_framework.add_raw_material(reinforced_iron_plate, 5)
    heavy_duty_modular_framework.add_raw_material(steel_tube, 20)
    heavy_duty_modular_framework.add_raw_material(reinforced_concrete_beam, 5)
    heavy_duty_modular_framework.add_raw_material(screw, 120)

    stator = FormulaCalculation(name="定子", rate=5, production=1, equipment_num=2)
    stator.add_raw_material(steel_tube, 3)
    stator.add_raw_material(electric_wire, 8)

    motor = FormulaCalculation(name="马达", rate=5, production=1, equipment_num=2)
    motor.add_raw_material(stator, 2)
    motor.add_raw_material(rotor, 2)

    automated_cables = FormulaCalculation(name="自动化电缆", rate=2.5, production=1, equipment_num=2)
    automated_cables.add_raw_material(stator, 1)
    automated_cables.add_raw_material(cable, 20)

    sturdy_steel_ingot = FormulaCalculation(name="坚固钢锭", rate=60, production=3, equipment_num=8)
    sturdy_steel_ingot.add_raw_material(iron_ingot, 2)
    sturdy_steel_ingot.add_raw_material(coal, 2)

    coarse_quartz = FormulaCalculation(name="粗石英", rate=30, production=1, equipment_num=0)

    quartz_crystal = FormulaCalculation(name="石英晶体", rate=22.5, production=3)
    quartz_crystal.add_raw_material(coarse_quartz, 5)
    # quartz_crystal.get_formula_sheet(60 / 5 * 3)  # 打印

    silicate = FormulaCalculation(name="二氧化硅", rate=37.5, production=5)
    silicate.add_raw_material(coarse_quartz, 3)
    # silicate.get_formula_sheet(60 / 3 * 5)  # 打印

    crude_oil = FormulaCalculation(name="原油", rate=120, production=1, equipment_num=9)

    heavy_oil_residue = FormulaCalculation(name="重油残渣", rate=30, production=1)

    plastics = FormulaCalculation(name="塑料", rate=20, production=2, equipment_num=10)
    plastics.add_raw_material(crude_oil, 3)
    plastics.add_after_product(heavy_oil_residue, 1)
    # plastics.get_formula_sheet(40)  # 40个塑料

    rubber = FormulaCalculation(name="橡胶", rate=20, production=2, equipment_num=10)
    rubber.add_raw_material(crude_oil, 3)
    rubber.add_after_product(heavy_oil_residue, 2)
    # rubber.get_formula_sheet(40)  # 40个橡胶

    fuel = FormulaCalculation(name="燃料", rate=40, production=4, equipment_num=10)
    fuel.add_raw_material(heavy_oil_residue, 6)
    # fuel.get_formula_sheet(40)

    circuit_board = FormulaCalculation(name="电路板", rate=7.5, production=1, equipment_num=2)
    circuit_board.add_raw_material(plastics, 4)
    circuit_board.add_raw_material(copperplate, 2)

    computer_chip = FormulaCalculation(name="计算机", rate=2.5, production=1, equipment_num=4)
    computer_chip.add_raw_material(circuit_board, 4)
    computer_chip.add_raw_material(cable, 8)
    computer_chip.add_raw_material(plastics, 16)

    modular_engine = FormulaCalculation(name="模块化引擎", rate=1, production=1, equipment_num=4)
    modular_engine.add_raw_material(motor, 2)
    modular_engine.add_raw_material(rubber, 15)
    # modular_engine.add_raw_material(smart_panel, 2)

    modular_engine.change_formula("钢锭", sturdy_steel_ingot)  # 原料配方替换：钢锭->坚固钢锭
    modular_engine.get_formula_sheet(5)  # 打印

    adaptive_control_unit = FormulaCalculation(name="自适应控制单元", rate=1, production=1, equipment_num=4)
    adaptive_control_unit.add_raw_material(automated_cables, 5)
    adaptive_control_unit.add_raw_material(circuit_board, 5)
    adaptive_control_unit.add_raw_material(heavy_duty_modular_framework, 1)
    adaptive_control_unit.add_raw_material(computer_chip, 2)

    qinjin_ore = FormulaCalculation(name="钦金矿石", rate=30, production=1)

    qinjin_bullion = FormulaCalculation(name="钦金锭", rate=15, production=1)
    qinjin_bullion.add_raw_material(qinjin_ore, 3)
    # qinjin_bullion.get_formula_sheet(80)  # 打印

    speed_wire = FormulaCalculation(name="极速电线", rate=60, production=5, equipment_num=1)
    speed_wire.add_raw_material(qinjin_bullion, 1)
    # speed_wire.get_formula_sheet(120/0.6)  # 打印

    # concrete.get_formula_sheet(40)

    smart_panel.get_formula_sheet(10)  # 10个智能嵌板

    # rotor.get_formula_sheet(10)  # 10个转子

    # reinforced_iron_plate.get_formula_sheet(10) # 10个强化铁板

    # copperplate.get_formula_sheet(30)  # 10个铜板

    # coal_power.get_formula_sheet(4)  # 8煤电

    # multifunctional_framework.change_formula("钢锭", sturdy_steel_ingot)  # 原料配方替换：钢锭->坚固钢锭
    # multifunctional_framework.get_formula_sheet(10)  # 10个多功能框架

    # modular_frame.get_formula_sheet(5) # 5个模块化框架

    # steel_beam.get_formula_sheet(60)  # 60个钢梁
